Skip to content

feat(core): add Forbid create object and Forbid edit object space permissions - #11149

Open
MichaelUray wants to merge 4 commits into
hcengineering:developfrom
MichaelUray:feat/space-forbid-create-edit
Open

MichaelUray wants to merge 4 commits into
hcengineering:developfrom
MichaelUray:feat/space-forbid-create-edit

Conversation

@MichaelUray

@MichaelUray MichaelUray commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #9079

Summary

Space type roles could only forbid deleting objects. This adds two space-scoped permissions, "Forbid create object" and "Forbid edit object", offered in every space type that already offers "Forbid delete object" (tracker, document, drive, board, recruit, lead, test management, GitHub).

Changes

  • Model: new core.permission.ForbidCreateObject (TxCreateDoc) and core.permission.ForbidUpdateObject (TxUpdateDoc), with labels and descriptions in all locales.
  • Server (SpacePermissionsMiddleware):
    • "Forbid edit object" also covers non-empty TxMixin (mixin attribute updates). Collection transactions are the child TxCreateDoc/TxUpdateDoc produced by createTxCollectionCUD and are checked the same way.
    • The check uses the transaction's objectSpace, so an update that moves an object out of a space where the member has "Forbid edit object" is rejected.
    • The generic create/edit forbids do not apply to the space document itself, so role holders can still manage members, role assignments and archiving. "Forbid delete object" keeps its existing scope and still applies to the space.
    • When several permissions without txMatch match, a forbidding one now wins instead of the last one in order. A matching txMatch permission keeps precedence.
    • Unchanged: the system account bypass uses the session identity (not modifiedBy), and trigger output (activity, notifications, collection and move side effects) goes through the derived entry below this middleware.
  • Client (view-resources/src/permissions.ts): canCreateObject returns false for "Forbid create object" and canRemoveDoc for "Forbid delete object", before either looks at class permissions. canChangeDoc returns false for "Forbid edit object" only after its existing checks of the class's own ${_class}_forbidden/${_class}_allowed permissions, so such an explicit class allow still makes the object editable in the UI. The existing early true results of all three helpers (permissions disabled, whitelisted space) are unchanged.

Behaviour changes and limitations

  • Forbid wins across all roles of a member. If a member's roles combine "Forbid delete object" with a class delete permission without txMatch (e.g. Drive "Remove file"), the outcome used to depend on permission order. Now the forbid always wins. The Drive UI follows this through canRemoveDoc.
  • Forbid create blocks all creates in the space, including comments, reactions, attachments and issue time reports (TimeSpendReport). "Forbid edit object" blocks editing them too.
  • UI coverage is partial. Only list/table inline editors, cards and Drive hide the affected controls. Other editors and create buttons (e.g. the issue editor and "New issue") stay enabled; the server rejects the change, but in several of them (e.g. custom-field editors, comments, moving an issue) the rejection is silent in the UI — nothing is saved, yet the user gets no error message. Better client feedback would be a separate change.
  • Moving an object into a forbidden space is not blocked. The move is checked against the source space only. Moving an object from an unrestricted space into a space where the member has "Forbid create object" still works.

Tests

foundations/server/packages/middleware/src/__tests__/spacePermissions.test.ts:

  • ForbidCreateObject blocks creating objects in the space only
  • ForbidUpdateObject blocks updates and mixin updates of objects in the space
  • rejects a TxMixin attribute update with Forbidden before forwarding
  • checks collection create/update transactions (real TxFactory)
  • ForbidUpdateObject blocks moving an object out of the source space
  • does not restrict members without the role
  • does not restrict the system account
  • does not grant the system bypass based on transaction modifiedBy
  • keeps the derived entry available for server transactions under both forbids
  • checks transactions inside TxApplyIf
  • does not lock role holders out of managing the space itself
  • keeps ForbidDeleteObject applying to the space itself
  • forbid wins over an allowing permission regardless of order
  • ForbidDeleteObject wins over an allowing delete permission (both orders)
  • a class/attribute permission with txMatch takes precedence over the generic forbid

plugins/view-resources/src/__tests__/permissions.test.ts:

  • canRemoveDoc: ForbidDeleteObject wins over a class delete permission (both orders), and a class delete permission alone still allows removal
  • canCreateObject / canChangeDoc: the create/edit forbids win over ordinary class create/update permissions (without txMatch), with allow-only controls

Verification (head 53090ee): local CI is green (install, build, validate, svelte-check, format, unit tests including middleware spacePermissions tests and view-resources permissions.test.ts, bundle). Browser checks on a local stack with two members, identical in Chromium, Firefox and WebKit: 9 checks pass — for a restricted member the server rejects a custom (mixin) field edit, creating and editing a comment, and moving an issue out of the project, and nothing is saved (verified after a reload), while an unrestricted member can do the same; managing members, role assignments and archiving the project keep working under both forbids; notifications and activity are still delivered. Not automated: attachment metadata editing, the system-session path, and the delete-precedence orderings (the Tracker role picker cannot assign an explicit delete allow); these are covered by the unit tests. Two observations: the attachment-create check could not locate the upload control reliably in this run, and, as noted above, several still-enabled editors give no visible error when the server rejects the change.

…missions

Space type roles could only forbid deleting objects. Add two more space-scoped
forbid permissions, 'Forbid create object' (TxCreateDoc) and 'Forbid edit object'
(TxUpdateDoc, which also covers mixin attribute updates), and offer them in all
space types that already offer 'Forbid delete object'.

They are enforced server-side by SpacePermissionsMiddleware like the existing
forbid permission and apply to all objects inside the space, including
comments and attachments. Two adjustments in the middleware:
- these generic create/update forbids are not applied to the space document
  itself, so role holders can still manage members, role assignments and
  archiving of the space;
- when several matching permissions without txMatch apply, a forbidding one
  now wins instead of depending on the role/permission order.

On the client, canCreateObject and canChangeDoc (used by inline attribute
editors, drive and card) honour the new permissions.

Fixes hcengineering#9079

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
@MichaelUray
MichaelUray force-pushed the feat/space-forbid-create-edit branch from c372297 to 6269ddd Compare October 19, 2026 06:42
@ArtyomSavchenko

Copy link
Copy Markdown
Member

@MichaelUray Thanks, the middleware change is small and the tests are welcome. A few questions:

  1. Are TxMixin and TxCollectionCUD wrappers covered? The description says TxUpdateDoc covers mixin attribute updates, but TxMixin is a separate tx class. Could you add a test for a mixin update and for an update wrapped in a collection tx?
  2. A TxUpdateDoc that changes space moves an object out of the space. Is that blocked by "Forbid edit object"? A test would help.
  3. Do server-side system transactions (activity messages, notifications, etc.) still go through for a space where both permissions are set? I'd expect them to bypass via the system account, but please confirm.
  4. The "forbidding wins" change also affects the existing "Forbid delete object" permission. Could you add a short note on that to the description and a regression test for the delete case?

… space forbids

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
The server now lets a forbidding permission win over an allowing one, so a
role with Forbid delete object and a class delete permission (e.g. Drive
'Remove file') can no longer delete. canRemoveDoc only looked at
class-specific delete permissions and still offered Delete, which then
failed. Check the generic delete forbid first, like canCreateObject and
canChangeDoc do for the create and edit forbids.

Add unit tests for the forbid checks in canRemoveDoc, canCreateObject and
canChangeDoc.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
@MichaelUray

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Answers in order:

  1. Yes, both are covered. "Forbid edit object" matches TxUpdateDoc and also any non-empty TxMixin (a mixin attribute update), so the mixin case is checked as an edit. Current core has no separate TxCollectionCUD wrapper anymore: TxFactory.createTxCollectionCUD returns the child TxCreateDoc/TxUpdateDoc with collection/attachedTo set, so it is checked like any other create or update. Tests: "ForbidUpdateObject blocks updates and mixin updates of objects in the space", "rejects a TxMixin attribute update with Forbidden before forwarding" and "checks collection create/update transactions" (built with the real factory).

  2. Yes. The check uses the transaction's current objectSpace, which is the source space, so an update that changes space is rejected for a member with "Forbid edit object" there; an unrestricted member can still move it. Test: "ForbidUpdateObject blocks moving an object out of the source space".

  3. Yes, they still go through. Trigger output (activity messages, notifications, collection and move side effects) is applied through context.derived, which MarkDerivedEntryMiddleware sets up below SpacePermissionsMiddleware in the server pipeline, so those transactions do not pass this check again. Services that connect with the system account are bypassed by the session identity check; a transaction whose modifiedBy is System but which comes from a normal member session gets no bypass. Tests: "does not restrict the system account", "does not grant the system bypass based on transaction modifiedBy" and "keeps the derived entry available for server transactions under both forbids".

  4. Added to the description. Before, when a member's roles combined "Forbid delete object" with a class delete permission without txMatch (e.g. Drive "Remove file"), the result depended on permission order and the last one won. Now the forbid always wins, across all roles the member has; a matching txMatch permission still takes precedence. Tests: "ForbidDeleteObject wins over an allowing delete permission in order …" (both orders), "keeps ForbidDeleteObject applying to the space itself" and "a class/attribute permission with txMatch takes precedence over the generic forbid".

Also changed:

  • On the client, canRemoveDoc and canCreateObject check their forbid before class permissions, so Drive no longer offers a Delete that the server would reject; canChangeDoc checks "Forbid edit object" after the class's own allow/forbid permissions (view-resources permissions.test.ts).
  • "Forbid create object" blocks every create in the space, including comments, reactions, attachments and issue time reports; "Forbid edit object" blocks editing them too.
  • The UI only partly reflects the new forbids (list/table inline editors, cards, Drive). Other editors and create buttons, e.g. the issue editor and "New issue", stay enabled; the server rejects the change and nothing is saved, but in several of them the rejection is currently silent in the UI (no error message). I noted that in the description as a limitation.
  • Moving an object from another space into a space where the member has "Forbid create object" is not blocked.

Verification (head 53090ee): local CI is green (install, build, validate, svelte-check, format, unit tests including middleware spacePermissions tests and view-resources permissions.test.ts, bundle). Browser checks on a local stack with two members, identical in Chromium, Firefox and WebKit: 9 checks pass — for a restricted member the server rejects a custom (mixin) field edit, creating and editing a comment, and moving an issue out of the project, and nothing is saved (verified after a reload), while an unrestricted member can do the same; managing members, role assignments and archiving the project keep working under both forbids; notifications and activity are still delivered. Not automated: attachment metadata editing, the system-session path, and the delete-precedence orderings (the Tracker role picker cannot assign an explicit delete allow); these are covered by the unit tests. Two observations: the attachment-create check could not locate the upload control reliably in this run, and, as noted above, several still-enabled editors give no visible error when the server rejects the change.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add new space permissions: forbid create, forbid edit

2 participants